using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

namespace Sandia.MVCFramework.Plugin
{
    /// <summary>
    /// A standard plugin source class. This plugin source obtains plugins by
    /// searching the given directory's subdirectories.
    /// </summary>
    public class StandardPluginSource : IPluginSource
    {
        /// <summary>
        /// The directory to get the plugins from.
        /// </summary>
        private string pluginDirectory;

        /// <summary>
        /// The loaded plugins.
        /// </summary>
        private List<IPlugin> availablePlugins = null;

        /// <summary>
        /// The directory to get the plugins from.
        /// </summary>
        public string PluginDirectory
        {
            get
            {
                return this.pluginDirectory;
            }
            set
            {
                this.pluginDirectory = value;
            }
        }

        /// <summary>
        /// Constructor for StandardPluginSource.
        /// </summary>
        /// <param name="pluginDirectory">
        /// The directory to get the plugins from.
        /// </param>
        public StandardPluginSource(
            string pluginDirectory)
        {
            this.pluginDirectory = pluginDirectory;
        }

        /// <summary>
        /// Gets a list of plugins in the given directory.
        /// </summary>
        /// <param name="pluginDirectory">The directory to search.</param>
        /// <returns>The list of plugins.</returns>
        private List<IPlugin> getPlugins(
            string pluginDirectory)
        {
            if(Directory.Exists(pluginDirectory))
            {
                // We create a seperate application domain to search for plugins,
                // This allows us to unload extraneous assemblies that are loaded
                // in the process of searching.
                AppDomain finderDomain = AppDomain.CreateDomain("PluginFinder");
                StandardPluginFinder pluginFinder = 
                    (StandardPluginFinder)finderDomain.CreateInstanceFromAndUnwrap(
                        Path.Combine(
                            Directory.GetCurrentDirectory(), 
                            "Sandia.MVCFramework.dll"),
                        "Sandia.MVCFramework.Plugin.StandardPluginFinder");
                IDictionary<Type, FileInfo> foundPlugins =
                    pluginFinder.Search(new DirectoryInfo(pluginDirectory));
                AppDomain.Unload(finderDomain);

                AppDomain appDomain = AppDomain.CurrentDomain;
                appDomain.AssemblyResolve += 
                    new ResolveEventHandler(appDomain_AssemblyResolve);

                List<IPlugin> plugins = new List<IPlugin>();
                // Loop through each found plugin dll
                foreach (KeyValuePair<Type, FileInfo> pluginInfo in foundPlugins)
                {
                    // Create an application domain for each plugin.
                    AppDomainSetup domainSetup = new AppDomainSetup();
                    domainSetup.ApplicationBase = pluginInfo.Value.DirectoryName;
                    AppDomain pluginDomain = 
                        AppDomain.CreateDomain(pluginInfo.Key.FullName, null, domainSetup);
                    IPlugin plugin = 
                        (IPlugin)pluginDomain.CreateInstanceFromAndUnwrap(
                            pluginInfo.Value.FullName, 
                            pluginInfo.Key.FullName);
                    plugins.Add(plugin);

                    //// Load the DLL assembly and initialize it
                    //Assembly assembly = Assembly.LoadFrom(fileInfo.FullName);
                    //// Loop through each of the types in the assembly and
                    //// check to see if it is a plugin.
                    //foreach (Type assemblyType in assembly.GetTypes())
                    //{
                    //    // If it is a plugin...
                    //    if (typeof(IPlugin).IsAssignableFrom(assemblyType))
                    //    {
                    //        // Get the plugin empty constructor.
                    //        ConstructorInfo pluginConstructorInfo =
                    //            assemblyType.GetConstructor(Type.EmptyTypes);
                    //        // If a constructor was found.
                    //        if (pluginConstructorInfo != null)
                    //        {
                    //            // Construct the plugin.
                    //            IPlugin plugin =
                    //                (IPlugin)pluginConstructorInfo.Invoke(
                    //                    new object[0]);
                    //            // Add the plugin.
                    //            plugins.Add(plugin);
                    //        }
                    //    }
                    //}
                }
                return plugins;
            }
            else
            {
                throw new DirectoryNotFoundException("Plugin directory not found!");
            }
        }

        /// <summary>
        /// This event represents a last-ditch effort to resolve an assembly.
        /// This event is fired after the GAC and executing directory are 
        /// checked. We use this event to walk the subdirectories in the plugin
        /// directory and check for the desired assembly. Where duplicates exist,
        /// the first match found is used.
        /// </summary>
        /// <param name="sender">The AppDomain.</param>
        /// <param name="args">The event arguments.</param>
        /// <returns>The resolved assembly.</returns>
        Assembly appDomain_AssemblyResolve(
            object sender, 
            ResolveEventArgs args)
        {
            // Get the assembly name.
            int endOfAssemblyName = args.Name.IndexOf(",");
            if(endOfAssemblyName == -1)
            {
                endOfAssemblyName = args.Name.Length;
            }
            string assemblyName = args.Name.Substring(0, endOfAssemblyName) + ".dll";
            // Walk the plugin directory's subdirectories.
            DirectoryInfo dirInfo = new DirectoryInfo(this.PluginDirectory);
            foreach (DirectoryInfo pluginDirInfo in dirInfo.GetDirectories())
            {
                string whereItMightBe = 
                    Path.Combine(
                        pluginDirInfo.FullName, 
                        args.Name.Substring(0, endOfAssemblyName)) + ".dll";
                // If the assembly is in this directory, load it and return.
                if (File.Exists(whereItMightBe))
                {
                    Assembly answer = Assembly.LoadFile(whereItMightBe);
                    return answer;
                }
            }
            // The assembly was not found.
            throw new FileLoadException(
                "Unable to load a dependant assembly: " + args.Name);
        }

        #region IPluginSource Members

        /// <summary>
        /// Gets a list of the available plugins from this source.
        /// </summary>
        /// <returns>The list of plugins.</returns>
        public IList<IPlugin> AvailablePlugins
        {
            get
            {
                if (this.availablePlugins == null)
                {
                    this.availablePlugins = 
                        this.getPlugins(this.pluginDirectory);
                }
                return this.availablePlugins;
            }
        }

        #endregion
    }
}
